package contactsrv

import (
	protos "goim-pro/api/protos/salty"
	"goim-pro/internal/app/models"
	. "goim-pro/internal/app/models/errors"
	. "goim-pro/internal/app/repos/contaceoperation"
	. "goim-pro/internal/app/repos/contact"
	. "goim-pro/internal/app/repos/user"
	"goim-pro/internal/app/services/converters"
	mysqlsrv "goim-pro/internal/db/mysql"
	"goim-pro/pkg/errors"
	"goim-pro/pkg/logs"
	"goim-pro/pkg/utils"
)

var (
	logger = logs.GetLogger("INFO")

	userRepo             IUserRepo
	contactRepo          IContactRepo
	contactOperationRepo IContactOperationRepo
	//notificationRepo   INotificationRepo
)

type IContactService interface {
	RequestContact(userId, contactId, reqReason string) (contactOperation *models.ContactOperation, tErr *TError)
	RefusedContact(userId, contactId, rejectReason string) (contactOperation *models.ContactOperation, tErr *TError)
	AcceptContact(userId, contactId string) (contactOperation *models.ContactOperation, contactProfile *models.Contact, tErr *TError)
	DeleteContact(userId, contactId string) (contactOperation *models.ContactOperation, tErr *TError)
	UpdateContactRemarkInfo(userId, contactId string, contactRemark *protos.ContactRemark) (contact *models.Contact, tErr *TError)
	GetContactList(userId string) (contacts []models.Contact, tErr *TError)
	GetContactOperationRelations(userId string, startDateTime, endDateTime int64) (relationGroup map[string][]models.ContactOperation, tErr *TError)
	DeleteContactOperation(userId string, relationId string, operationId string) (tErr *TError)
	DeleteContactOperationRelation(userId string, relationId string) (tErr *TError)
}

type ContactService struct {
}

func New() IContactService {
	db := mysqlsrv.GetMysql()

	userRepo = NewUserRepo(db)
	contactRepo = NewContactRepo(db)
	contactOperationRepo = NewContactOperationRepo(db)
	//notificationRepo = NewNotificationRepo(db)

	return &ContactService{}
}

// RequestContact: request add contact
func (cs *ContactService) RequestContact(userId, contactId, reqReason string) (operationMessage *models.ContactOperation, tErr *TError) {
	contact, err := userRepo.FindByUserId(contactId)
	if err != nil {
		logger.Errorf("find contact by userId error: %s", err.Error())
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}
	if contact == nil {
		return nil, NewTError(protos.StatusCode_STATUS_BAD_REQUEST, errmsg.ErrInvalidContact)
	}

	isExists, err := contactRepo.IsContactExists(userId, contactId)
	if err != nil {
		logger.Errorf("checking contact error: %s", err.Error())
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}
	if isExists {
		return nil, NewTError(protos.StatusCode_STATUS_BAD_REQUEST, errmsg.ErrContactAlreadyExists)
	}

	// overwrite history request operation messages' status, then create a new one
	prevOperation, _ := updateContactOperationStatus(userId, contactId, models.TypeOfRequest, models.StsOverwrite)
	requestOperation := &models.ContactOperation{
		OpsId:         utils.NewULID(0),
		RelationId:    utils.NewULID(1),
		SenderId:      userId,
		RecipientId:   contactId,
		OperationType: models.TypeOfRequest,
		Reason:        reqReason,
		Status:        models.StsPending,
	}
	if prevOperation != nil {
		requestOperation.RelationId = prevOperation.RelationId // use previous records' id
	}
	err = contactOperationRepo.CreateContactOperation(requestOperation)
	if err != nil {
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}

	// TODO: send notification
	// ...

	operationMessage, err = getContactOperationDetail(requestOperation.OpsId)
	return operationMessage, nil
}

// refused request contact
func (cs *ContactService) RefusedContact(userId, contactId, rejectReason string) (contactOperation *models.ContactOperation, tErr *TError) {
	contact, err := userRepo.FindByUserId(contactId)
	if err != nil {
		logger.Errorf("find contact by userId error: %s", err.Error())
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}
	if contact == nil {
		return nil, NewTError(protos.StatusCode_STATUS_BAD_REQUEST, errmsg.ErrInvalidContact)
	}

	isExists, err := contactRepo.IsContactExists(userId, contactId)
	if err != nil {
		logger.Errorf("checking contact error: %s", err.Error())
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}
	if isExists {
		return nil, NewTError(protos.StatusCode_STATUS_BAD_REQUEST, errmsg.ErrContactAlreadyExists)
	}

	reqOperation, rErr := updateContactOperationStatus(contactId, userId, models.TypeOfRequest, models.StsReceived)
	if rErr != nil {
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, rErr)
	}
	if reqOperation == nil {
		return nil, NewTError(protos.StatusCode_STATUS_NOT_FOUND, errmsg.ErrIllegalOperation)
	}

	rejectOperation := &models.ContactOperation{
		OpsId:         utils.NewULID(2),
		RelationId:    reqOperation.RelationId,
		SenderId:      userId,
		RecipientId:   contactId,
		OperationType: models.TypeOfReject,
		Reason:        rejectReason,
		Status:        models.StsPending,
	}
	err = contactOperationRepo.CreateContactOperation(rejectOperation)
	if err != nil {
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}

	// TODO: send notification
	// ...

	contactOperation, err = getContactOperationDetail(rejectOperation.OpsId)

	return contactOperation, nil
}

// accept contact request
func (cs *ContactService) AcceptContact(userId, contactId string) (contactOperation *models.ContactOperation, contactProfile *models.Contact, tErr *TError) {
	contact, err := userRepo.FindByUserId(contactId)
	if err != nil {
		logger.Errorf("find contact by userId error: %s", err.Error())
		return nil, nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}
	if contact == nil {
		return nil, nil, NewTError(protos.StatusCode_STATUS_BAD_REQUEST, errmsg.ErrInvalidContact)
	}

	isExists, err := contactRepo.IsContactExists(userId, contactId)
	if err != nil {
		logger.Errorf("checking contact error: %s", err.Error())
		return nil, nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}
	if isExists {
		return nil, nil, NewTError(protos.StatusCode_STATUS_BAD_REQUEST, errmsg.ErrContactAlreadyExists)
	}

	requestOperation, rErr := updateContactOperationStatus(contactId, userId, models.TypeOfRequest, models.StsReceived)
	_, rErr = updateContactOperationStatus(userId, contactId, models.TypeOfRequest, models.StsReceived)
	if rErr != nil {
		return nil, nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, rErr)
	}
	if requestOperation == nil {
		return nil, nil, NewTError(protos.StatusCode_STATUS_NOT_FOUND, errmsg.ErrIllegalOperation)
	}

	err = handleAcceptContact(userId, contactId)
	if err != nil {
		logger.Errorf("insert contact error: %s", err.Error())
		return nil, nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}

	acceptOperation := &models.ContactOperation{
		OpsId:         utils.NewULID(3),
		RelationId:    requestOperation.RelationId,
		SenderId:      userId,
		RecipientId:   contactId,
		OperationType: models.TypeOfAccept,
		Status:        models.StsPending,
	}
	err = contactOperationRepo.CreateContactOperation(acceptOperation)
	if err != nil {
		return nil, nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}

	// TODO: send notification
	// ...

	contactProfile, err = contactRepo.FindOne(map[string]interface{}{"userId": userId, "contactId": contactId})
	contactOperation, err = getContactOperationDetail(acceptOperation.OpsId)

	return contactOperation, contactProfile, nil
}

// delete contact
func (cs *ContactService) DeleteContact(userId, contactId string) (contactOperation *models.ContactOperation, tErr *TError) {
	isExists, err := contactRepo.IsContactExists(userId, contactId)
	if err != nil {
		logger.Errorf("checking contact error: %s", err.Error())
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}
	if !isExists {
		return nil, NewTError(protos.StatusCode_STATUS_BAD_REQUEST, errmsg.ErrContactNotExists)
	}

	if err = handleDeleteContact(userId, contactId); err != nil {
		logger.Errorf("remove contact error: %s", err.Error())
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}

	deleteOperation := &models.ContactOperation{
		OpsId:         utils.NewULID(4),
		RelationId:    utils.NewULID(5),
		SenderId:      userId,
		RecipientId:   contactId,
		OperationType: models.TypeOfDelete,
		Status:        models.StsPending,
	}
	err = contactOperationRepo.CreateContactOperation(deleteOperation)
	if err != nil {
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}

	// TODO: send notification
	// ...

	contactOperation, err = getContactOperationDetail(deleteOperation.OpsId)

	return
}

// update contact remark profile
func (cs *ContactService) UpdateContactRemarkInfo(userId, contactId string, contactRemark *protos.ContactRemark) (contact *models.Contact, tErr *TError) {
	isExists, err := contactRepo.IsContactExists(userId, contactId)
	if err != nil {
		logger.Errorf("checking contact error: %s", err.Error())
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}
	if !isExists {
		return nil, NewTError(protos.StatusCode_STATUS_BAD_REQUEST, errmsg.ErrContactNotExists)
	}

	contact, err = handleUpdateContactRemark(userId, contactId, contactRemark)
	if err != nil {
		logger.Errorf("update contact remark error: %s", err.Error())
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}
	return contact, nil
}

// query user's contact list
func (cs *ContactService) GetContactList(userId string) (contacts []models.Contact, tErr *TError) {
	// TODO: should consider the black list function
	criteria := map[string]interface{}{
		"userId": userId,
	}
	contacts, err := contactRepo.FindAll(criteria)
	if err != nil {
		logger.Errorf("query user contacts error: %s", err.Error())
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}

	return contacts, nil
}

func (cs *ContactService) GetContactOperationRelations(userId string, startDateTime, endDateTime int64) (relationGroup map[string][]models.ContactOperation, tErr *TError) {
	var contactOperations []models.ContactOperation
	var err error
	if startDateTime == endDateTime && startDateTime == -1 {
		contactOperations, err = contactOperationRepo.FindAllByUserId(userId)
	} else {
		startDateStr := utils.ParseTimestampToDateTimeStr(startDateTime, utils.MysqlDateTimeFormat)
		endDateStr := utils.ParseTimestampToDateTimeStr(endDateTime, utils.MysqlDateTimeFormat)
		contactOperations, err = contactOperationRepo.FindAllByUserIdWithTimeRange(userId, startDateStr, endDateStr)
	}
	if err != nil {
		logger.Errorf("query user's contact operation list error: %s", err.Error())
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}

	relationGroup = make(map[string][]models.ContactOperation)
	for _, operation := range contactOperations {
		relationGroup[operation.RelationId] = append(relationGroup[operation.RelationId], operation)
	}

	return relationGroup, nil
}

func (cs *ContactService) DeleteContactOperation(userId string, relationId string, operationId string) (tErr *TError) {
	_, err := contactOperationRepo.DeleteContactOperationById(operationId, false)
	if err != nil {
		logger.Errorf("delete contact operation failed, %v", err)
		return NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}
	return nil
}

func (cs *ContactService) DeleteContactOperationRelation(userId string, relationId string) (tErr *TError) {
	err := contactOperationRepo.DeleteContactOperationRelation(userId, relationId, false)
	if err != nil {
		logger.Errorf("delete contact operation failed, %v", err)
		return NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}
	return nil
}

func handleAcceptContact(userId string, contactId string) (err error) {
	newContact1 := &models.Contact{
		UserId:    userId,
		ContactId: contactId,
	}
	newContact2 := &models.Contact{
		UserId:    contactId,
		ContactId: userId,
	}

	err = contactRepo.InsertContacts(newContact1, newContact2)
	if err != nil {
		return err
	}
	return nil
}

func handleDeleteContact(userId string, contactId string) (err error) {
	tx := mysqlsrv.GetMysql().Begin()
	txContactRepo := NewContactRepo(tx)
	if err = txContactRepo.RemoveContactsByIds(userId, contactId); err != nil {
		logger.Errorf("remove contact err: %s", err.Error())
		tx.Rollback()
		return
	}
	if err = txContactRepo.RemoveContactsByIds(contactId, userId); err != nil {
		tx.Rollback()
		return
	}
	tx.Commit()
	return err
}

func handleUpdateContactRemark(userId string, contactId string, pbProfile *protos.ContactRemark) (contact *models.Contact, err error) {
	criteria := map[string]interface{}{
		"UserId":    userId,
		"ContactId": contactId,
	}

	remarkProfile := converters.ConvertProto2EntityForRemarkProfile(pbProfile)
	updateMap := utils.TransformStructToMap(remarkProfile)

	contact, err = contactRepo.FindOneAndUpdateRemark(criteria, updateMap)
	return
}

//func dispatchNotificationMessage(params *dispatchNotificationParams) (ntfMsg *models.Notification, err error) {
//	// save new notification message
//	psId := utils.NewULID(0)
//	notification := &models.Notification{
//		NtfId:        utils.NewULID(1),
//		PsId:         psId,
//		SenderId:     params.senderId,
//		RecipientId:  params.recipientId,
//		ObjectName:   models.ObjectNameCustomContact,
//		Content:      params.content,
//		PushContent:  params.content,
//		IsNeedRemind: true,
//		NtfStatus:    models.NtfStatusOfPending,
//		Channel:      models.ChannelOfUser,
//		PushExt: models.PushExt{
//			PsId:     psId,
//			Title:    params.title,
//			Classify: models.ClassOfContactOperation,
//			Extra:    params.strExtra,
//		},
//	}
//
//	ntfMsg, err = notificationRepo.InsertOne(notification)
//	if err != nil {
//		logger.Errorf("insert new notification error: %s", err.Error())
//		return nil, err
//	}
//
//	// TODO: send notification
//	logger.Info(ntfMsg)
//	return ntfMsg, nil
//}

//func refreshNotificationMessageStatus(targetId, senderId string, msgType string, operation protos.ContactOperationMessage_OperationType, status string) (ntfOperation *models.Notification, err error) {
//	ntfCondition := make(map[string]interface{})
//	ntfCondition["senderId"] = senderId
//	ntfCondition["targetId"] = targetId
//	ntfCondition["msgType"] = msgType
//
//	ntfUpdated := &models.Notification{
//		IsNeedRemind: false,
//		NtfStatus:       status,
//	}
//	var ntf = new(models.Notification)
//	tx := mysqlsrv.GetMysql().Begin()
//	if err := tx.Table(tbl.TableNotifications).Where(ntfCondition).Update(ntfUpdated).First(ntf).Error; err != nil {
//		tx.Rollback()
//		logger.Errorf("error happened to update notification: %v", err)
//		return nil, err
//	}
//	msgCondition := make(map[string]interface{})
//	msgCondition["messageId"] = ntf.MessageId
//	msgUpdated := &models.PushExt{
//		TemplateId: int32(operation),
//	}
//	if err := tx.Table(tbl.TableNotificationMsgs).Where(msgCondition).Update(msgUpdated).Error; err != nil {
//		tx.Rollback()
//		logger.Errorf("error happened to update notification msg: %v", err)
//		return nil, err
//	}
//	err = tx.Commit().Error
//
//	ntfOperation, err = getContactOperationDetail(ntf.NtfId)
//	if err != nil {
//		return nil, nil
//	}
//	return ntfOperation, err
//}

//func refreshNotificationMessage(ntfCondition map[string]interface{}, msgUpdated *models.PushExt) (ntf *models.Notification, err error) {
//	ntf, err = notificationRepo.FindOne(ntfCondition)
//	if err != nil {
//		logger.Errorf("error happened to update notification message: %v", err)
//		return nil, err
//	}
//	if ntf == nil {
//		return nil, nil
//	}
//
//	msgCondition := map[string]interface{}{"messageId": ntf.MessageId}
//	if err = notificationRepo.UpdateOneMessage(msgCondition, msgUpdated); err != nil {
//		logger.Errorf("error happened to update notification msg: %v", err)
//		return ntf, err
//	}
//	return ntf, err
//}

func getContactOperationDetail(opsId string) (operationMessage *models.ContactOperation, err error) {
	operationMessage, err = contactOperationRepo.FindOne(map[string]interface{}{"opsId": opsId})
	if err != nil {
		logger.Errorf("query user contact's operation message error: %s", err.Error())
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, err)
	}
	return operationMessage, nil
}

func updateContactOperationStatus(senderId, recipientId string, operationType int32, status models.OperationStatus) (*models.ContactOperation, error) {
	updated := &models.ContactOperation{
		Status: status,
	}
	contactOperation, rErr := contactOperationRepo.FindLatestAndUpdateMany(senderId, recipientId, operationType, updated)
	if rErr != nil {
		return nil, NewTError(protos.StatusCode_STATUS_INTERNAL_SERVER_ERROR, rErr)
	}
	return contactOperation, nil
}
